package com.xunji.user.zip;

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.FileHeader;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;

/**
 * 增强版压缩文件解压工具类
 * 支持格式：ZIP/7Z/TAR/GZIP/BZIP2/TAR.GZ
 * 支持加密压缩包（ZIP/7Z）
 * 依赖：commons-compress + commons-io + zip4j
 */
public class AdvancedCompressionUtils {

    public static Logger log = LoggerFactory.getLogger(AdvancedCompressionUtils.class);

    /**
     * 解压任意压缩文件（自动检测格式）
     *
     * @param sourceFile 压缩文件路径
     * @param targetDir  解压目标目录
     * @param password   密码（可选，null 表示无密码）
     * @return 0 解压成功 1需要密码 2格式不支持 3文件找不到 4 解压失败
     */
    public static int decompress(String sourceFile, String targetDir, char[] password) {
        File file = new File(sourceFile);
        if (!file.exists()) {
            log.error("找不到文件，path：{}", sourceFile);
            return 3; // 文件找不到
        }

        try {
            String extension = FilenameUtils.getExtension(sourceFile).toLowerCase();
            Path targetPath = Paths.get(targetDir);
            Files.createDirectories(targetPath);

            switch (extension) {
                case "zip":
                case "jar":
                case "war":
                    decompressZipWithPassword(sourceFile, targetPath, password);
                    break;
                case "7z":
                    decompress7zWithPassword(sourceFile, targetPath, password);
                    break;
                case "tar":
                    decompressArchive(sourceFile, targetPath);
                    break;
                case "gz":
                case "gzip":
                    decompressGzip(sourceFile, targetPath);
                    break;
                case "bz2":
                case "bzip2":
                    decompressBzip2(sourceFile, targetPath);
                    break;
                case "tgz":
                case "tar.gz":
                    decompressTarGz(sourceFile, targetPath);
                    break;
                default:
                    throw new UnsupportedOperationException("Unsupported format: " + extension);
            }
        } catch (Exception e) {
            if (e.getMessage().contains("Wrong password")
                    || e.getMessage().contains("requires password")
                    || e.getMessage().contains("without a password")
                    || e.getMessage().contains("Compressed data is corrupt")) {
                log.error("密码解压失败或密码不正确，path：{}", sourceFile);
                e.printStackTrace();
                return 1; // 需要密码
            } else if (e.getMessage().contains("not supported") || e.getMessage().contains("Unsupported format")) {
                log.error("不支持的文件格式，path：{}", sourceFile);
                e.printStackTrace();
                return 2; //
            } else {
                log.error("文件：{}，解压失败！", sourceFile);
                e.printStackTrace();
                return 4;
            }
        }
        log.info("文件：{}，解压成功！", sourceFile);
        return 0;
    }

    /**
     * 解压ZIP文件（支持加密）
     */
    private static void decompressZipWithPassword(String sourceFile, Path targetDir, char[] password)
            throws IOException {
        ZipFile zipFile = new ZipFile(sourceFile);

        // 检测加密并设置密码
        if (zipFile.isEncrypted()) {
            if (password == null || password.length == 0) {
                throw new IOException("Encrypted ZIP requires password");
            }
            zipFile.setPassword(password);
        }

        // 解决中文文件名问题
        List<FileHeader> headers = zipFile.getFileHeaders();
        for (FileHeader header : headers) {
            Path entryPath = targetDir.resolve(header.getFileName());
            safeCreateFile(entryPath, header.isDirectory());

            if (!header.isDirectory()) {
                try (InputStream is = zipFile.getInputStream(header)) {
                    Files.copy(is, entryPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    /**
     * 解压7Z文件（支持加密）
     */
    private static void decompress7zWithPassword(String sourceFile, Path targetDir, char[] password)
            throws IOException {
        SevenZFile sevenZFile;
        if (password != null && password.length > 0) {
            sevenZFile = new SevenZFile(new File(sourceFile), password);
        } else {
            sevenZFile = new SevenZFile(new File(sourceFile));
        }

        try {
            SevenZArchiveEntry entry;
            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (entry.isDirectory()) continue;

                Path entryPath = targetDir.resolve(entry.getName());
                safeCreateFile(entryPath, false);

                try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(entryPath))) {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = sevenZFile.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                }
            }
        } finally {
            sevenZFile.close();
        }
    }

    // --------------- 以下为非加密格式的解压方法 --------------- 

    // 通用归档格式解压（TAR）
    private static void decompressArchive(String sourceFile, Path targetDir)
            throws IOException, ArchiveException {
        try (InputStream is = Files.newInputStream(Paths.get(sourceFile));
             ArchiveInputStream ais = new ArchiveStreamFactory()
                     .createArchiveInputStream(ArchiveStreamFactory.TAR, is)) {

            ArchiveEntry entry;
            while ((entry = ais.getNextEntry()) != null) {
                if (!ais.canReadEntryData(entry)) continue;
                Path entryPath = targetDir.resolve(entry.getName());
                safeCreateFile(entryPath, entry.isDirectory());
                if (!entry.isDirectory()) {
                    Files.copy(ais, entryPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    // GZIP解压（单文件）
    private static void decompressGzip(String sourceFile, Path targetDir)
            throws IOException, CompressorException {
        Path targetFile = targetDir.resolve(FilenameUtils.getBaseName(sourceFile));
        try (InputStream fi = Files.newInputStream(Paths.get(sourceFile));
             CompressorInputStream cis = new CompressorStreamFactory()
                     .createCompressorInputStream(CompressorStreamFactory.GZIP, fi)) {
            Files.copy(cis, targetFile, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    // BZIP2解压（单文件）
    private static void decompressBzip2(String sourceFile, Path targetDir)
            throws IOException, CompressorException {
        Path targetFile = targetDir.resolve(FilenameUtils.getBaseName(sourceFile));
        try (InputStream fi = Files.newInputStream(Paths.get(sourceFile));
             CompressorInputStream cis = new CompressorStreamFactory()
                     .createCompressorInputStream(CompressorStreamFactory.BZIP2, fi)) {
            Files.copy(cis, targetFile, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    // TAR.GZ解压
    private static void decompressTarGz(String sourceFile, Path targetDir)
            throws IOException, CompressorException, ArchiveException {
        try (InputStream fi = Files.newInputStream(Paths.get(sourceFile));
             CompressorInputStream cis = new CompressorStreamFactory()
                     .createCompressorInputStream(CompressorStreamFactory.GZIP, fi);
             ArchiveInputStream ais = new ArchiveStreamFactory()
                     .createArchiveInputStream(ArchiveStreamFactory.TAR, cis)) {

            ArchiveEntry entry;
            while ((entry = ais.getNextEntry()) != null) {
                if (!ais.canReadEntryData(entry)) continue;
                Path entryPath = targetDir.resolve(entry.getName());
                safeCreateFile(entryPath, entry.isDirectory());
                if (!entry.isDirectory()) {
                    Files.copy(ais, entryPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    // 安全创建文件（防止路径穿越）
    private static void safeCreateFile(Path path, boolean isDirectory) throws IOException {
        Path normalized = path.normalize();
        Path parentDir = normalized.getParent();

        if (parentDir == null || !Files.exists(parentDir)) {
            Files.createDirectories(parentDir != null ? parentDir : path.getRoot());
        }

        if (isDirectory) {
            Files.createDirectories(normalized);
        } else {
            Files.createDirectories(normalized.getParent());
            if (!Files.exists(normalized)) {
                Files.createFile(normalized);
            }
        }
    }

    // 使用示例
    public static void main(String[] args) {

        int decompress = AdvancedCompressionUtils.decompress("D:\\xunji\\xunji-user.7z", "D:\\xunji\\", "123".toCharArray());

    }
}